package config

import (
	"crypto/tls"
	"errors"
	"os"
	"shell/common"
	"shell/utils"

	"github.com/go-playground/validator/v10"
	"github.com/google/uuid"
	"gopkg.in/yaml.v3"
)

type ProxyRuleYaml struct {
	Action string  `yaml:"action" validate:"required,oneof=proxy direct"`
	Dest   string  `yaml:"dest" validate:"required,tcp4_addr"`
	To     *string `yaml:"to" validate:"uuid,required_if=Action proxy"`
}

type RedirRuleYaml struct {
	LocalPort uint16 `yaml:"local_port" validate:"required"`
	RedirAddr string `yaml:"redir_addr" validate:"required,tcp4_addr"`
	UUID      string `yaml:"uuid" validate:"uuid"`
}

type Socks5CfgYaml struct {
	Enable    bool            `yaml:"enable" validate:"required"`
	Port      uint16          `yaml:"port" validate:"required"`
	ProxyRule []ProxyRuleYaml `yaml:"proxy_rules"`
}

type PortRedirYaml struct {
	Enable    bool            `yaml:"enable" validate:"required"`
	RedirRule []RedirRuleYaml `yaml:"redir_rules"`
}

type ClientConfigYaml struct {
	ProxyAddr  string `yaml:"proxy_addr" validate:"required,udp4_addr"`
	CaBase64   string `yaml:"ca_base64" validate:"required,base64"`
	CertBase64 string `yaml:"cert_base64" validate:"required,base64"`
	KeyBase64  string `yaml:"key_base64" validate:"required,base64"`

	Socks5 *Socks5CfgYaml `yaml:"socks5"`
	Redir  *PortRedirYaml `yaml:"redir"`
}

type ProxyRule struct {
	Action  string
	Dest    string
	To      *uuid.UUID
	Matcher *utils.IPOrHost
}

type Socks5Config struct {
	Port      uint16
	ProxyRule []ProxyRule
}

func (s5c Socks5Config) Match(str string) *ProxyRule {
	for _, r := range s5c.ProxyRule {
		if r.Matcher.Match(str) {
			return &r
		}
	}
	return nil
}

type RedirRule struct {
	LocalPort uint16
	RedirAddr string
	UUID      uuid.UUID
}

type ClientConfig struct {
	TLS       *tls.Config
	ProxyAddr string
	Socks5    *Socks5Config
	Redir     []RedirRule // 本地转发规则
}

func (cc *ClientConfig) FromYaml(cfgYaml *ClientConfigYaml) error {
	cc.ProxyAddr = cfgYaml.ProxyAddr
	tlsCfg, err := common.TlsCfg(cfgYaml.CaBase64, cfgYaml.CertBase64, cfgYaml.KeyBase64, false)
	if err != nil {
		return err
	}
	cc.TLS = tlsCfg
	// socks5代理相关配置
	if cfgYaml.Socks5 != nil && cfgYaml.Socks5.Enable {
		cc.Socks5 = new(Socks5Config)
		cc.Socks5.Port = cfgYaml.Socks5.Port
		cc.Socks5.ProxyRule = make([]ProxyRule, 0)
		for _, v := range cfgYaml.Socks5.ProxyRule {
			pr := ProxyRule{}
			if v.To != nil {
				id, err := uuid.Parse(*v.To)
				if err != nil {
					return err
				}
				pr.To = &id
			} else {
				if v.Action == "redir" {
					return errors.New("proxy but not have uuid")
				}
			}
			pr.Action = v.Action
			pr.Dest = v.Dest
			pr.Matcher, err = utils.NewIPOrHost(v.Dest)
			if err != nil {
				return err
			}
			cc.Socks5.ProxyRule = append(cc.Socks5.ProxyRule, pr)
		}
	}

	// 端口转发相关配置
	if cfgYaml.Redir != nil && cfgYaml.Redir.Enable {
		cc.Redir = make([]RedirRule, 0)
		for _, v := range cfgYaml.Redir.RedirRule {
			id, err := uuid.Parse(v.UUID)
			if err != nil {
				return err
			}
			dr := new(RedirRule)
			dr.UUID = id
			dr.LocalPort = v.LocalPort
			_, _, err = utils.ParseIpPort(v.RedirAddr)
			if err != nil {
				return err
			}
			dr.RedirAddr = v.RedirAddr
			cc.Redir = append(cc.Redir, *dr)
		}
	}
	return nil
}

func ParseConfig(path string) (*ClientConfig, error) {
	content, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	cfgYaml := new(ClientConfigYaml)
	err = yaml.Unmarshal(content, cfgYaml)
	if err != nil {
		return nil, err
	}
	vali := validator.New(validator.WithRequiredStructEnabled())
	err = vali.Struct(cfgYaml)
	if err != nil {
		return nil, err
	}
	result := new(ClientConfig)
	err = result.FromYaml(cfgYaml)
	return result, err
}
